use std::{default::Default, env, fs::read_to_string};

use enquote::unquote;
use getopts::Options;

use super::error::Error;

const RUNFILE: &str = "/run/greetd.run";
const GENERAL_SERVICE: &str = "greetd";
const GREETER_SERVICE: &str = "greetd-greeter";

#[derive(Debug, Eq, PartialEq, Default)]
pub struct ConfigSession {
    pub command: String,
    pub user: String,
    pub service: String,
}

#[derive(Debug, Eq, PartialEq, Default)]
pub struct ConfigInternal {
    pub session_worker: usize,
}

#[derive(Debug, Eq, PartialEq)]
pub struct ConfigGeneral {
    pub source_profile: bool,
    pub runfile: String,
    pub service: String,
}

impl Default for ConfigGeneral {
    fn default() -> Self {
        ConfigGeneral {
            source_profile: true,
            runfile: RUNFILE.to_string(),
            service: GENERAL_SERVICE.to_string(),
        }
    }
}

#[derive(Debug, Eq, PartialEq, Default)]
pub struct ConfigFile {
    pub general: ConfigGeneral,
    pub default_session: ConfigSession,
    pub initial_session: Option<ConfigSession>,
}

#[derive(Debug, Eq, PartialEq)]
pub struct Config {
    pub file: ConfigFile,
    pub internal: ConfigInternal,
}

fn print_usage(program: &str, opts: Options) {
    let brief = format!("Usage: {} [options]", program);
    println!("{}", opts.usage(&brief));
    println!("For more details, see greetd(1).");
}

fn maybe_unquote(s: &str) -> Result<String, Error> {
    Ok(match s.chars().next() {
        Some('"') | Some('\'') => unquote(s).map_err(|e| Error::ConfigError(format!("{}", e)))?,
        _ => s.to_string(),
    })
}

fn parse_config(config_str: &str) -> Result<ConfigFile, Error> {
    let general = Default::default();

    let default_session = {
        let commandstr = "usrman session --access=physical --ui=gui --business=workstation";
        let command = maybe_unquote(commandstr)
            .map_err(|e| format!("unable to read default_session.command: {}", e))?;

        let user = maybe_unquote(config_str)
            .map_err(|e| format!("unable to read default_session.user: {}", e))?;

        let service = maybe_unquote(&GREETER_SERVICE)
            .map_err(|e| format!("unable to read default_session.service: {}", e))?;

        ConfigSession {
            command,
            user,
            service,
        }
    };

    let initial_session = None;

    Ok(ConfigFile {
        initial_session,
        default_session,
        general,
    })
}

pub fn read_config() -> Result<Config, Error> {
    let args: Vec<String> = env::args().collect();
    let program = args[0].clone();
    let mut opts = Options::new();
    opts.optflag("h", "help", "print this help menu");
    opts.optopt("s", "socket-path", "socket path to use", "SOCKET_PATH");
    opts.optopt("c", "config", "config file to use", "CONFIG_FILE");
    opts.optopt(
        "w",
        "session-worker",
        "start a session worker (internal)",
        "FD",
    );
    let matches = match opts.parse(&args[1..]) {
        Ok(m) => m,
        Err(f) => return Err(format!("could not parse arguments: {}", f).into()),
    };
    if matches.opt_present("h") {
        print_usage(&program, opts);
        std::process::exit(0);
    }

    let internal = ConfigInternal {
        session_worker: matches
            .opt_get("session-worker")
            .expect("unable to parse session-worker")
            .unwrap_or(0),
    };

    if internal.session_worker > 0 {
        return Ok(Config {
            file: Default::default(),
            internal,
        });
    }

    let config_str = match matches.opt_str("config") {
        Some(v) => read_to_string(v),
        None => read_to_string("/etc/greetd/greetd.conf")
            .or_else(|_| read_to_string("/etc/greetd/config.toml")),
    }?;
    let file = parse_config(&config_str)?;

    if file.default_session.command.is_empty() {
        return Err(Error::ConfigError(
            "no default session user specified".to_string(),
        ));
    }
    if file.default_session.user.is_empty() {
        return Err(Error::ConfigError(
            "no default session user specified".to_string(),
        ));
    }
    if let Some(s) = &file.initial_session {
        if s.user.is_empty() {
            return Err(Error::ConfigError(
                "initial session enabled but contained no user".to_string(),
            ));
        }
        if s.command.is_empty() {
            return Err(Error::ConfigError(
                "initial session enabled but contained no command".to_string(),
            ));
        }
    }

    Ok(Config { file, internal })
}
